#ifndef __MY_UTIL__
#define __MY_UTIL__

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <memory>
#include <experimental/filesystem> // c++17文件管理系统头文件
#include "bundle.h" // 压缩文件的第三方库
#include <jsoncpp/json/json.h>


namespace hhbcloud
{
    // 简化命名空间
    namespace fs = std::experimental::filesystem;
    class FileUtil
    {
    public:
        FileUtil(const std::string& filename)
            :_filename(filename)
        {
        }

        bool Remove()
        {
            if (remove(_filename.c_str()) == 0)
            {
                return true;
            }
            return false;
        }

        // 获取文件大小
        size_t FileSize()
        {
            struct stat st; // 使用stat来获取文件的各种信息
            if (stat(_filename.c_str(), &st) < 0) // stat函数成功返回0，失败返回-1
            {
                std::cout << "get file size error" << std::endl;
                return -1;
            }

            return st.st_size;
        }

        // 获取文件最后一次修改时间
        time_t LastMTime()
        {
            struct stat st;
            if (stat(_filename.c_str(), &st) < 0)
            {
                std::cout << "get lastmtime error" << std::endl;
                return -1;
            }

            return st.st_mtime;
        }

        // 获取文件最后一次访问时间
        time_t LastATime()
        {
            struct stat st;
            if (stat(_filename.c_str(), &st) < 0)
            {
                std::cout << "get lastatime error" << std::endl;
                return -1;
            }

            return st.st_atime;
        }

        // 获取文件路径中的文件名称
        std::string FileName()
        {
            size_t pos = _filename.find_last_of("/"); // 从后往前找第一个/
            if (pos == std::string::npos)
            {
                // 此时没有找到/说明文件路径就是文件名称
                return _filename;
            }

            return _filename.substr(pos + 1);
        }


        // 设置文件内容
        bool SetContent(const std::string& body)
        {
            // 打开文件
            std::ofstream ofs;
            ofs.open(_filename, std::ios::binary); // 以二进制形式打开文件，若文件不存在会自动创建文件
            if (ofs.is_open() == false)
            {
                std::cout << "write open file error" << std::endl;
                return false;
            }

            // 打开文件成功后，将body的内容写到文件中
            ofs.write(&body[0], body.size());
            if (ofs.good() == false) // 判断文件写入过程中是否出错
            {
                std::cout << "write file error" << std::endl;
                ofs.close();
                return false;
            }

            ofs.close();
            return true;
        }

        // 读取文件指定位置 指定长度的内容, body用来接收获取到的数据
        bool GetPosLen(std::string* body, size_t pos, size_t len)
        {
            // 判断 pos + len 是否超出文件大小
            size_t size = FileSize();
            if (pos + len > size)
            {
                std::cout << "pos + len more than file size" << std::endl;
                return false;
            }

            // 读的形式打开文件
            std::ifstream ifs;
            ifs.open(_filename.c_str(), std::ios::binary);//二进制形式打开
            if (ifs.is_open() == false) // 打开失败报错
            {
                std::cout << "read open file error" << std::endl;
                return false;
            }

            // 打开成功后，先为body开辟足够的大小
            body->resize(len); // 避免多次安装

            // 从文件起始位置定位到pos位置
            ifs.seekg(pos, std::ios::beg);
            // 读取内容
            ifs.read(&(*body)[0], len);
            if (ifs.good() == false) // 判断读取文件过程是否出错
            {
                std::cout << "read file error" << std::endl;
                ifs.close();
                return false;
            }
            ifs.close();
            return true;
        }

        // 读取整个文件的内容
        bool GetContent(std::string* body)
        {
            size_t size = FileSize();
            return GetPosLen(body, 0, size); // 复用获取文件指定位置指定长度函数
        }

        // 判断文件是否存在
        bool Exists()
        {
            return fs::exists(_filename); // c++17文件系统的函数判断文件是否存在
        }

        // 创建目录
        bool CreateDirectory()
        {
            if (Exists() == true) return true; // 若文件存在则不必创建
            return fs::create_directories(_filename); // 创建目录时，传入路径，则创建多级目录
        }
        // 获取指定目录下的所有文件名，并存放到array数组中
        bool ScanDirectory(std::vector<std::string>* array)
        {
            // 使用 fs::directory_iterator 函数获取指定目录下的所有文件
            for (auto& file : fs::directory_iterator(_filename))
            {
                // 应该判断获取的文件是否是目录，因为目录下可能还有目录
                if (fs::is_directory(file) == true) // is_derectory 判断是否是目录
                {
                    continue; // 如果是目录文件则跳过
                }

                // file并不是string类对象，可以使用path函数实例化，relative_path获取相对路径，string带路径的string的文件名
                array->push_back(fs::path(file).relative_path().string());
            }
        }


        // 压缩文件
        bool Compress(const std::string& packname)
        {
            // 先将文件读出来
            std::string body;
            if (GetContent(&body) == false)
            {
                std::cout << "compress get file content error" << std::endl;
                return false;
            }

            // 使用pack函数对读出来的内容进行压缩
            std::string packed = bundle::pack(bundle::LZIP, body); // packed中存储的即为压缩后的数据

            // 将压缩后的文件写入packname文件中
            FileUtil fu(packname);
            if (fu.SetContent(packed) == false)
            {
                std::cout << "compress set packname content error" << std::endl;
                return false;
            }
            return true;
        }

        // 文件解压缩
        bool Uncompress(const std::string& unpackname)
        {
            // 先读取文件内容
            std::string body;
            if (GetContent(&body) == false)
            {
                std::cout << "uncompress get file content error" << std::endl;
                return false;
            }

            // 使用unpack解压缩文件
            std::string unpacked = bundle::unpack(body); // unpacked存储的是解压缩后的数据

            // 将解压后的文件写到解压文件中
            FileUtil fu(unpackname);
            if (fu.SetContent(unpacked) == false)
            {
                std::cout << "uncompress set file content error" << std::endl;
                return false;
            }
            return true;

        }
    private:
        std::string _filename;
    };

    class JsonUtil // json工具目的是封装序列化和反序列化的函数
    {
    public:
        // JsonUtil的序列化和反序列化是一种封装比较单一，不需要创建对象调用成员函数，所以将成员函数设置成静态
        static bool Serialization(Json::Value& root, std::string* str) // 讲一个结构体序列化为字符串
        {
            Json::StreamWriterBuilder swb; // 创建一个 json::StreamWriterBuilder 对象
            // 使用智能指针管理一个由swb创建的Json::StreamWriter 对象
            std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
            std::stringstream ss; // 创建stringstream，将root序列化后的字符串写入stringstream
            // 使用sw的write函数将root写入stringstream
            if (sw->write(root, &ss) != 0)
            {
                std::cout << "Serialization write error" << std::endl;
                return false;
            }

            *str = ss.str();
            return true;
        }

        static bool Deserialization(const std::string& str, Json::Value* root) // 反序列化就是将一个字符串，反序列化成一个json结构体
        {
            Json::CharReaderBuilder crb; // 创建一个Json::CharReaderBuilder
            // 使用智能指针管理crb创建的son::CharReader对象
            std::unique_ptr<Json::CharReader> cr(crb.newCharReader());

            std::string err; // 用来记录错误信息，cr->parse函数要求必须err参数
            if (cr->parse(str.c_str(), str.c_str() + str.size(), root, &err) == false)
            {
                std::cout << "Deserialization parse error" << std::endl;
                return false;
            }
            return true;
        }
    };
}

#endif
